package tech.waterism.algorithm.moea.GA_SEPT;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.moeaframework.Executor;
import org.moeaframework.core.NondominatedPopulation;
import org.moeaframework.core.Solution;
import org.moeaframework.core.variable.RealVariable;
import tech.waterism.constant.StatisticsConstants;
import tech.waterism.constant.StringConstants;
import tech.waterism.dto.*;
import tech.waterism.entity.FMCaliparascope;
import tech.waterism.entity.FMParameter;
import tech.waterism.utils.CalibrationUtils;

import java.util.*;
import java.util.stream.Collectors;

public class MoeaCalculateSept {
//    public static CalibrationMoeaDto getCalibrationParamsDirectMoea(CalibrationMoeaDto dto, String uuid)
//            throws Exception {
//        int numberOfVariables = 0;
//        for (FPInputDto fpInputDto : dto.getFpInputs()) {
//            for (FMIndexDto fmIndexDto : fpInputDto.getMdcdList()) {
//                if (fmIndexDto.getCaliParascopes() != null) {
//                    fmIndexDto.getCaliParascopes().sort(Comparator.comparing(FMCaliparascope::getNorow));
//                    numberOfVariables += fmIndexDto.getCaliParascopes().size();
//                }
//            }
//        }
//        //
//        Algorithm algoObject = dto.getAlgorithm();
//        //获取算法与优化目标
//        Integer algorithmMaxEvaluations = algoObject.getMaxEvaluations();
//        Map<String, String> algorithmAttribute = algoObject.getAttributes();
//        if (algoObject.getObjectives() == null || algoObject.getObjectives().size() == 0) {
//            throw new Exception("未设置目标函数");
//        }
//        List<String> objectivesForAlgorithm = algoObject.getObjectives();
//        int numberOfObjectives = objectivesForAlgorithm.size();
//        Executor problemExecute = new Executor();
//        CaliForecastProblemSept caliForecastProblem = new CaliForecastProblemSept(numberOfVariables, numberOfObjectives,
//                objectivesForAlgorithm.toArray(new String[0]), dto);
//        problemExecute.withProblem(caliForecastProblem).withAlgorithm(algoObject.getName())
//                .withProgressListener(new MyProgressListener(uuid, algorithmMaxEvaluations));
//        problemExecute.withMaxEvaluations(algorithmMaxEvaluations);
//        for (Map.Entry<String, String> entry : algorithmAttribute.entrySet()) {
//            problemExecute.withProperty(entry.getKey(), entry.getValue());
//        }
//        NondominatedPopulation problemExecuteResult = problemExecute.run();
//
//        List<ModelCalibrationResult> modelCalibrationResultList = new LinkedList<>();
//        for (Solution solution : problemExecuteResult) {
//            List<Variable> variables = new LinkedList<>();
//            ModelCalibrationResult item = new ModelCalibrationResult();
//            for (int i = 0; i < solution.getNumberOfVariables(); i++) {
//                RealVariable value = (RealVariable) solution.getVariable(i);
//                Variable variable =
//                        new Variable().setLowerBound(value.getLowerBound()).setUpperBound(value.getUpperBound())
//                                .setValue(value.getValue());
//                variables.add(variable);
//            }
//            item.setVariables(variables);
//            item.setObjectives(solution.getObjectives());
//            modelCalibrationResultList.add(item);
//        }
//        System.out.println("modelCalibrationResultList start");
//        System.out.println(modelCalibrationResultList);
//        System.out.println("modelCalibrationResultList end");
//        ModelCalibrationResult modelCalibrationResult;
//        double floodVolumeErrorWeight;
//        double floodPeakErrorWeight;
//        double peakTimeErrorWeight;
//        double coefficientOfDeterminationWeight;
//        double coefficientOfDeterminationSingleWeight;
//        Map<String, Double> objectiveWeight = dto.getAlgorithm().getObjectiveWeight();
//        if (objectiveWeight != null) {
//            floodVolumeErrorWeight = objectiveWeight.getOrDefault(StringConstants.FLOOD_VOLUME_ERROR, 0.05);
//            floodPeakErrorWeight = objectiveWeight.getOrDefault(StringConstants.FLOOD_PEAK_ERROR, 0.22);
//            peakTimeErrorWeight = objectiveWeight.getOrDefault(StringConstants.PEAK_TIME_ERROR, 0.22);
//            coefficientOfDeterminationWeight =
//                    objectiveWeight.getOrDefault(StringConstants.COEFFICIENT_OF_DETERMINATION, 0.5);
//            coefficientOfDeterminationSingleWeight =
//                    objectiveWeight.getOrDefault(StringConstants.COEFFICIENT_OF_DETERMINATION_SINGLE, 0.5);
//        } else {
//            floodVolumeErrorWeight = 0.05;
//            floodPeakErrorWeight = 0.22;
//            peakTimeErrorWeight = 0.22;
//            coefficientOfDeterminationWeight = 0.5;
//            coefficientOfDeterminationSingleWeight = 0.5;
//        }
//        if (modelCalibrationResultList.size() > 1) {
//            modelCalibrationResult =
//                    getBestResult(modelCalibrationResultList, objectivesForAlgorithm, floodVolumeErrorWeight,
//                            floodPeakErrorWeight, peakTimeErrorWeight, coefficientOfDeterminationWeight,
//                            coefficientOfDeterminationSingleWeight);
//        } else {
//            modelCalibrationResult = modelCalibrationResultList.get(0);
//        }
//        //forecast with cali result
//        int index = 0;
//        String[] types = new String[numberOfVariables];
//        double[] paras = new double[numberOfVariables];
//        List<Variable> variables = modelCalibrationResult.getVariables();
//        for (FPInputDto fpInput : dto.getFpInputs()) {
//            for (FMIndexDto fmIndexDto : fpInput.getMdcdList()) {
//                if (fmIndexDto.getCaliParascopes() != null) {
//                    for (FMCaliparascopeDto caliParascope : fmIndexDto.getCaliParascopes()) {
//                        caliParascope.setValue(variables.get(index));
//                        types[index] = caliParascope.getType();
//                        paras[index] = variables.get(index).getValue();
//                        index++;
//                    }
//                }
//            }
//        }
//        FitnessCalculationSept fitnessCalculation =
//                new FitnessCalculationSept(dto, paras, types, objectivesForAlgorithm.toArray(new String[0]));
//        double[] fitness = fitnessCalculation.getFitness();
//        System.out.println("paras: " + Arrays.toString(paras));
//        System.out.println("fitness: " + Arrays.toString(fitness));
//        CalibrationMoeaDto result = fitnessCalculation.getForcast();
//        result.setObjective(getObjective(objectivesForAlgorithm.toArray(new String[0]), fitness));
//        complePara(result);
//        // 恢复纳什系数
//        CalibrationUtils.revertNashCoeff(result.getObjective(), objectivesForAlgorithm.toArray(new String[0]));
//        // fitnessCalculation.updateNewStatus(result);
//        return result;
//    }

    /**
     * 目标值mapping
     */
    public static Map<String, Double> getObjective(String[] objectives, double[] fitness) {
        Map<String, Double> result = new HashMap<>();
        for (int i = 0; i < objectives.length; i++) {
            result.put(objectives[i], fitness[i]);
        }
        return result;
    }

    /**
     * 筛选最优解 -- 按权重
     */
    private static ModelCalibrationResult getBestResult(List<ModelCalibrationResult> modelCalibrationResultList,
            List<String> objectivesForAlgorithm, double floodVolumeErrorWeight, double floodPeakErrorWeight,
            double peakTimeErrorWeight, double coefficientOfDeterminationWeight,
            double coefficientOfDeterminationSingleWeight) {
        int floodVolumeErrorIndex = objectivesForAlgorithm.indexOf(StringConstants.FLOOD_VOLUME_ERROR);
        int floodPeakErrorIndex = objectivesForAlgorithm.indexOf(StringConstants.FLOOD_PEAK_ERROR);
        int peakTimeErrorIndex = objectivesForAlgorithm.indexOf(StringConstants.PEAK_TIME_ERROR);
        int coefficientOfDeterminationIndex =
                objectivesForAlgorithm.indexOf(StringConstants.COEFFICIENT_OF_DETERMINATION);
        int coefficientOfDeterminationSingleIndex =
                objectivesForAlgorithm.indexOf(StringConstants.COEFFICIENT_OF_DETERMINATION_SINGLE);
        double minFitness = Double.MAX_VALUE;
        int minIndex = -1;
        double curFitness;
        for (int i = 0; i < modelCalibrationResultList.size(); i++) {
            ModelCalibrationResult item = modelCalibrationResultList.get(i);
            curFitness = 0;
            if (floodVolumeErrorIndex != -1) {
                curFitness += item.getObjectives()[floodVolumeErrorIndex] * floodVolumeErrorWeight;
            }
            if (floodPeakErrorIndex != -1) {
                curFitness += item.getObjectives()[floodPeakErrorIndex] * floodPeakErrorWeight;
            }
            if (peakTimeErrorIndex != -1) {
                curFitness += item.getObjectives()[peakTimeErrorIndex] * peakTimeErrorWeight;
            }
            if (coefficientOfDeterminationIndex != -1) {
                curFitness += item.getObjectives()[coefficientOfDeterminationIndex] * coefficientOfDeterminationWeight;
            }
            if (coefficientOfDeterminationSingleIndex != -1) {
                curFitness += item.getObjectives()[coefficientOfDeterminationSingleIndex] *
                        coefficientOfDeterminationSingleWeight;
            }
            if (curFitness < minFitness) {
                minFitness = curFitness;
                minIndex = i;
            }
        }
        if (minIndex == -1) {
            minIndex = 0;
        }
        return modelCalibrationResultList.get(minIndex);
    }

    private static void complePara(CalibrationMoeaDto result) {
        for (FPInputDto fpInput : result.getFpInputs()) {
            for (FMIndexDto fmIndexDto : fpInput.getMdcdList()) {
                if (fmIndexDto.getMdclb().equals(StatisticsConstants.AUTO_CALIBRATE)) {
                    Map<String, String> paramMap = new HashMap<>();
                    if (fmIndexDto.getExtraParameters() != null && fmIndexDto.getExtraParameters().size() > 0) {
                        paramMap.putAll(fmIndexDto.getExtraParameters().stream().collect(
                                Collectors.toMap(parameter -> parameter.getParameter().trim().split("=")[0].trim(),
                                        parameter -> parameter.getParameter().trim().split("=")[1].trim())));
                    }
                    if (fmIndexDto.getCaliParascopes() != null) {
                        for (FMCaliparascopeDto caliParascope : fmIndexDto.getCaliParascopes()) {
                            switch (caliParascope.getType()) {
                                case StringConstants.INT:
                                    paramMap.put(caliParascope.getParam(),
                                            Double.valueOf(caliParascope.getValue().getValue()).intValue() + "");
                                    break;
                                case StringConstants.DOUBLE:
                                    paramMap.put(caliParascope.getParam(), caliParascope.getValue().getValue() + "");
                                    break;
                            }
                        }
                    }
                    List<FMParameter> parameters = fmIndexDto.getParameters();
                    for (FMParameter parameter : parameters) {
                        if (!parameter.getParameter().contains("=")) {
                            continue;
                        }
                        String key = parameter.getParameter().trim().split("=")[0].trim();
                        if (paramMap.containsKey(key)) {
                            parameter.setParameter(key + "=" + paramMap.get(key));
                        }
                    }
                }
            }
        }
    }

    public static List<CalibrationMoeaDto> generSeptData(FPIndexDto fpIndexDto, List<FPInputDto> fpInputs,
            List<Integer> indexList) {
        List<CalibrationMoeaDto> result = new LinkedList<>();
        int endI = 0;
        int bak = 0;
        for (int i = 0; i < indexList.size(); i++) {
            int startI = i == 0 ? 0 : bak;
            endI = indexList.get(i) + endI;
            bak = endI;
            CalibrationMoeaDto item = new CalibrationMoeaDto();
            FPIndexDto fpIndex = SerializationUtils.clone(fpIndexDto);
            item.setFpIndex(fpIndex);
            List<ForecastDataDto> indexData = fpIndexDto.getData().subList(startI, endI);
            item.getFpIndex().setData(indexData);
            List<FPInputDto> fpInputDtos = new LinkedList<>();
            for (FPInputDto fpInput : fpInputs) {
                FPInputDto fpInputDto = SerializationUtils.clone(fpInput);
                fpInputDto.setData(fpInput.getData().subList(startI, endI));
                fpInputDtos.add(fpInputDto);
            }
            item.setFpInputs(fpInputDtos);
            result.add(item);
        }
        return result;
    }

    public static List<Integer> countDataNum(List<DateInterval> intervalList, Integer clen, String unt, Integer prdt) {
        unt = (unt == null) ? "H" : unt;
        List<Integer> cutIndexList = new ArrayList<>();
        for (DateInterval dateInterval : intervalList) {
            int dataNum = countNum(dateInterval.getStart(), dateInterval.getEnd(), clen, unt, prdt);
            cutIndexList.add(dataNum);
        }
        return cutIndexList;
    }

    private static int countNum(Date start, Date end, Integer clen, String unt, Integer prdt) {
        Date startR = DateUtils.addDays(start, -prdt);
        long diff;
        switch (unt) {
            case "H":
                diff = DateUtil.between(startR, end, DateUnit.HOUR);
                return (int) diff / clen + 1;
            case "M":
                diff = DateUtil.between(startR, end, DateUnit.MINUTE);
                return (int) diff / clen + 1;
        }
        return 0;
    }
}